package com.life.utils;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import com.life.constants.Constants;
import com.life.model.LifeModel;
import com.life.statistics.StatisticStruct;
import com.life.statistics.StatisticsElement;

public class StatisticsUtil
{
    public static StatisticStruct calculateStatistics(
	    ArrayList<boolean[][]> models,
	    ProgressIndicatorHelper progressIndicator)
    {
	StatisticStruct result = new StatisticStruct();
	int size = models.size();
	int xBounds = models.get(0).length-2;
	int yBounds = models.get(0)[0].length-2;
	StatisticsElement[][] elements = new StatisticsElement[xBounds][yBounds];

	double steps = 100d/size;
	double increment = 100/(double)steps;
	double current = 0;
	progressIndicator.startIndication();
	
	int births = 0;
	int deaths = 0;
	int life = LoggerUtil.getLife(0);
	int minLife = Integer.MAX_VALUE;
	int maxLife = Integer.MIN_VALUE;
	result.setStartPopulation(life);
	
	for (int i = 0; i < size; i++)
	{
	   boolean[][] model = models.get(i);
	   
	    for (int j = 0; j < xBounds; j++)
	    {
		for (int k = 0; k < yBounds; k++)
		{
		    if (elements[j][k] == null)
		    {
			elements[j][k] = new StatisticsElement(j,k);
		    }

		    Double val = model[j + 1][k + 1] ? new Double(1)
			    : new Double(0);
		    elements[j][k].add(val);
	   
		}
	    }
	    
	    int thisLife = LoggerUtil.getLife(i);
	    
	    if(thisLife > maxLife)
	    {
		maxLife = thisLife;
	    }
	    if(thisLife < minLife)
	    {
		minLife = thisLife;
	    }
	    
	    if(thisLife > life)
	    {
		births += (thisLife - life);
	    }
	    else 
	    {
		deaths+=(life-thisLife);
	    }
	    
	    life = thisLife;
	    
	    current += steps;
	    progressIndicator.updateProcess((int) (current));
	}
	    
	result.setElements(elements); 
	result.setEndingPopulation(life);
	result.setTotalBirths(births);
	result.setTotalDeaths(deaths);
	result.setGenerations(size);
	result.setMinLife(minLife);
	result.setMaxLife(maxLife);
	
	/* find total births */
	
	progressIndicator.stopIndication();
	
	
	
	return result;
    }

    public static HashMap<String,ArrayList> calculateLifeGraph(StatisticStruct results)
    {
	HashMap<String,ArrayList> returnVal = new HashMap<String,ArrayList>();
	
	ArrayList<Integer>livesList = new ArrayList<Integer>();
		
	for(int i = 0; i < results.getLifes().length;i++)
	{
	    livesList.add(new Integer(results.getLifes()[i]));
	}
	
	returnVal.put("Life",livesList);
	 
	return returnVal;
    }
    
    public static HashMap<String,ArrayList> calculateDeathGraph(StatisticStruct results)
    {
	HashMap<String,ArrayList> returnVal = new HashMap<String,ArrayList>();
	
	ArrayList<Integer>deathsList = new ArrayList<Integer>();
		
	for(int i = 0; i < results.getDeaths().length;i++)
	{
	    deathsList.add(new Integer(results.getDeaths()[i]));
	}
	
	returnVal.put("Deaths",deathsList);
	
	return returnVal;
    }
    
    public static HashMap<String,ArrayList> calculateBirthChangeGraph(StatisticStruct results)
    {
	HashMap<String,ArrayList> returnVal = new HashMap<String,ArrayList>();
  
	ArrayList<Integer>changeList = new ArrayList<Integer>();
	
	int currentLife = results.getLifes()[0];
	
	changeList.add(new Integer(0));
	
	for(int i = 1; i < results.getLifes().length;i++)
	{
	    changeList.add(new Integer(results.getLifes()[i]-currentLife));
	    currentLife = results.getLifes()[i];
	}
	
	returnVal.put("Births",changeList);
	
	return returnVal;
	  
    }
    
    public static ClusterStruct calculateClusters(StatisticStruct struct)
    {
	return processClusterHelper(struct);
    }
    
    public static int count = 0;
    public static ClusterStruct processClusterHelper(StatisticStruct struct)
    {
	
	
	int xSize = struct.elements.length;
	int ySize = struct.elements[0].length;
	boolean [][] marked = new boolean[xSize][ySize];
	
	ClusterStruct result = new ClusterStruct(xSize, ySize);
	
	ArrayList<StatisticsElement>orderedLocations = getElementsInOrderofSum(struct.flattenElements(struct.elements));
	int clusterValue = 0;
	
	for(int i = 0; i < orderedLocations.size();i++)
	{
	    count = 0;
	    StatisticsElement element = orderedLocations.get(i);
	    
	    if(!marked[element.getBoardX()][element.getBoardY()] && element.getSum() > 0)
	    {
		count++;
		findClusters(result,
			struct.elements,
			element,
			element.getBoardX(),
			element.getBoardY(),
			marked,
			++clusterValue,
			element.getSum(),
			struct.getLifeStandardDeviation());
		
		result.setClusterCount(clusterValue,count);
	    }
	    else if(!marked[element.getBoardX()][element.getBoardY()] && element.getSum() == 0)
	    {
		marked[element.getBoardX()][element.getBoardY()]  = true;
	    }
	    
	}
	
	return result;
    }


    
    private static void findClusters(ClusterStruct result, 
	    StatisticsElement[][] elements, 
	    StatisticsElement thisElement,
	    int x, 
	    int y,
	    boolean[][] marked, 
	    int value,
	    double lifeCount,
	    double lifeStd
	  )
    {
	marked[x][y] = true;
	result.setInCluster(x, y, value);
	
	if(lifeCount > 0)
	{
	
	/* look at all the neighbor cells */
	for(int i = -1; i <=1 ;i++)
	{
	    for(int j = -1; j <=1; j++)
	    {
		/* dont look at the same cell*/
		if(i != 0 || j != 0)
		{
		    int currentX = x+i;
		    int currentY = y+j;
		    
		    if(0 <= currentX && currentX < elements.length &&
		       0 <= currentY && currentY < elements[currentX].length   &&
		       !marked[currentX][currentY]
		    )
		    {
			    StatisticsElement currentElement = elements[currentX][currentY];
			    double thisSum = currentElement.getSum();
			   
			    double diffUp = lifeCount + (lifeStd * 2);
			    double diffDown = lifeCount - (lifeStd * 2);
			    if(thisSum > 0 
				&& 
				    thisSum >= diffDown 
				   &&
				    
				    thisSum <=diffUp
				    )
			    {
				count++;
				findClusters(result,
					elements,
					currentElement,
					currentX,
					currentY,
					marked,
					value,
					currentElement.getSum(),
					currentElement.getStdDev()
				//lifeStd	
				);
			    }		    
		    }
		}
	    }
	}
	}
    }

    private  static ArrayList<StatisticsElement> getElementsInOrderofSum(StatisticsElement[] elements)
    {
	ArrayList<StatisticsElement> result = new ArrayList<StatisticsElement>();
	
	for(int i = 0; i < elements.length;i++)
	{
	    result.add(elements[i]);
	}
	
	Collections.sort(result,new StatisticsElementSumComparator());
	
	return result;
    }
    
    static  class  StatisticsElementSumComparator implements Comparator
    {
	
	public int compare(Object o1, Object o2)
	{
	    StatisticsElement lhs = (StatisticsElement)o1;
	    StatisticsElement rhs = (StatisticsElement)o2;
	    
	    return (int)(rhs.getSum() - lhs.getSum());
	   
	}
	
    }  
}
